/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.vcs.cmdline.exec;
import org.netbeans.modules.vcs.util.*;
import java.io.*;
import java.util.*;
import java.text.*;
import org.apache.regexp.*;
import org.openide.util.*;
/** Single external command to be executed. See {@link TestCommand} for typical usage.
*
* @author Michal Fadljevic
*/
//-------------------------------------------
public class ExternalCommand {
private Debug E=new Debug("ExternalCommand",true); // NOI18N
private Debug D=new Debug("ExternalCommand",true); // NOI18N
public static final int SUCCESS=0;
public static final int FAILED=1;
public static final int FAILED_ON_TIMEOUT=2;
private String command=null;
private long timeoutMilis=240000;
private int exitStatus=SUCCESS;
private String inputData=null;
private Object stdoutLock=new Object();
private RegexListener[] stdoutListeners=new RegexListener[0];
private RE[] stdoutRegexps=new RE[0];
private Object stderrLock=new Object();
private RegexListener[] stderrListeners=new RegexListener[0];
private RE[] stderrRegexps=new RE[0];
private Object stdOutErrLock = new Object(); // synchronizes stdout and stderr
private Vector stdoutNoRegexListeners = new Vector();
private Vector stderrNoRegexListeners = new Vector();
/*
private volatile Vector commandOutput = null;
private static final String STDOUT = "Following output comes from the Standard Output of the command:"; // NOI18N
private static final String STDERR = "Following output comes from the Error Output of the command:"; // NOI18N
*/
//-------------------------------------------
public ExternalCommand(){
}
//-------------------------------------------
public ExternalCommand(String command){
setCommand(command);
}
//-------------------------------------------
public ExternalCommand(String command, long timeoutMilis){
setCommand(command);
setTimeout(timeoutMilis);
}
//-------------------------------------------
public ExternalCommand(String command, long timeoutMilis, String input){
setCommand(command);
setTimeout(timeoutMilis);
setInput(input);
}
//-------------------------------------------
public void setCommand(String command){
this.command=command;
}
//-------------------------------------------
public void setTimeout(long timeoutMilis){
this.timeoutMilis=timeoutMilis;
}
//-------------------------------------------
public void setInput(String inputData){
this.inputData=inputData;
}
//-------------------------------------------
private void setExitStatus(int exitStatus){
this.exitStatus=exitStatus;
}
//-------------------------------------------
public int getExitStatus(){
return exitStatus;
}
//-------------------------------------------
private String[] parseParameters(String s) {
int NULL = 0x0; // STICK + whitespace or NULL + non_"
int INPARAM = 0x1; // NULL + " or STICK + " or INPARAMPENDING + "\ // NOI18N
int INPARAMPENDING = 0x2; // INPARAM + \
int STICK = 0x4; // INPARAM + " or STICK + non_" // NOI18N
int STICKPENDING = 0x8; // STICK + \
Vector params = new Vector(5,5);
char c;
int state = NULL;
StringBuffer buff = new StringBuffer(20);
int slength = s.length();
for (int i = 0; i < slength; i++) {
c = s.charAt(i);
if (Character.isWhitespace(c)) {
if (state == NULL) {
params.addElement(buff.toString());
buff.setLength(0);
} else if (state == STICK) {
params.addElement(buff.toString());
buff.setLength(0);
state = NULL;
} else if (state == STICKPENDING) {
buff.append('\\');
params.addElement(buff.toString());
buff.setLength(0);
state = NULL;
} else if (state == INPARAMPENDING) {
state = INPARAM;
buff.append('\\');
buff.append(c);
} else { // INPARAM
buff.append(c);
}
continue;
}
if (c == '\\') {
if (state == NULL) {
++i;
if (i < slength) {
char cc = s.charAt(i);
if (cc == '"' || cc == '\\') {
buff.append(cc);
} else if (Character.isWhitespace(cc)) {
buff.append(c);
--i;
} else {
buff.append(c);
buff.append(cc);
}
} else {
buff.append('\\');
break;
}
continue;
} else if (state == INPARAM) {
state = INPARAMPENDING;
} else if (state == INPARAMPENDING) {
buff.append('\\');
state = INPARAM;
} else if (state == STICK) {
state = STICKPENDING;
} else if (state == STICKPENDING) {
buff.append('\\');
state = STICK;
}
continue;
}
if (c == '"') {
if (state == NULL) {
state = INPARAM;
} else if (state == INPARAM) {
state = STICK;
} else if (state == STICK) {
state = INPARAM;
} else if (state == STICKPENDING) {
buff.append('"');
state = STICK;
} else { // INPARAMPENDING
buff.append('"');
state = INPARAM;
}
continue;
}
if (state == INPARAMPENDING) {
buff.append('\\');
state = INPARAM;
} else if (state == STICKPENDING) {
buff.append('\\');
state = STICK;
}
buff.append(c);
}
// collect
if (state == INPARAM) {
params.addElement(buff.toString());
} else if ((state & (INPARAMPENDING | STICKPENDING)) != 0) {
buff.append('\\');
params.addElement(buff.toString());
} else { // NULL or STICK
if (buff.length() != 0) {
params.addElement(buff.toString());
}
}
String[] ret = new String[params.size()];
params.copyInto(ret);
return ret;
}
//-------------------------------------------
public int exec(){
//D.deb("exec()"); // NOI18N
Process proc=null;
Thread stdoutThread=null;
Thread stderrThread=null;
StdoutGrabber stdoutGrabber=null;
StderrGrabber stderrGrabber=null;
WatchDog watchDog = null;
//commandOutput = new Vector();
try{
//D.deb("Thread.currentThread()="+Thread.currentThread()); // NOI18N
String[] commandArr=parseParameters(command);
D.deb("commandArr="+MiscStuff.arrayToString(commandArr)); // NOI18N
/*
if (commandArr.toLowerCase().endsWith(".class")) {
execClass(commandArr);
}
*/
try{
proc=Runtime.getRuntime().exec(commandArr);
}
catch (IOException e){
E.err("Runtime.exec failed."); // NOI18N
stderrNextLine(g("EXT_CMD_RuntimeFailed", command)); // NOI18N
setExitStatus(FAILED);
return getExitStatus();
}
watchDog=new WatchDog("VCS-WatchDog",timeoutMilis,Thread.currentThread(), proc); // NOI18N
// timeout 0 means no dog is waitng to eat you
if (timeoutMilis>0) {
watchDog.start();
}
//D.deb("New WatchDog with timeout = "+timeoutMilis); // NOI18N
stdoutGrabber=new StdoutGrabber(proc.getInputStream());
stdoutThread=new Thread(stdoutGrabber,"VCS-StdoutGrabber"); // NOI18N
stderrGrabber=new StderrGrabber(proc.getErrorStream());
stderrThread=new Thread(stderrGrabber,"VCS-StderrGrabber"); // NOI18N
stdoutThread.start();
stderrThread.start();
if( inputData!=null ){
try{
DataOutputStream os=new DataOutputStream(proc.getOutputStream());
//D.deb("stdin>>"+inputData); // NOI18N
os.writeChars(inputData);
os.flush();
os.close();
}
catch(IOException e){
E.err(e,"writeBytes("+inputData+") failed"); // NOI18N
}
}
int exit=proc.waitFor();
//D.deb("process exit="+exit); // NOI18N
//D.deb("stdoutThread.join()"); // NOI18N
stdoutThread.join();
//D.deb("stderrThread.join()"); // NOI18N
stderrThread.join();
//D.deb("watchDog.cancel()"); // NOI18N
//watchDog.cancel();
setExitStatus( exit==0 ? SUCCESS : FAILED );
}
catch(InterruptedException e){
D.deb("Ring from the WatchDog."); // NOI18N
String[] commandArr=parseParameters(command);
D.deb("commandArr="+MiscStuff.arrayToString(commandArr)); // NOI18N
//e.printStackTrace();
//D.deb("Stopping StdoutGrabber."); // NOI18N
stopThread(stdoutThread,stdoutGrabber);
//D.deb("Stopping StderrGrabber."); // NOI18N
stopThread(stderrThread,stderrGrabber);
//D.deb("Destroy process."); // NOI18N
proc.destroy();
setExitStatus(FAILED_ON_TIMEOUT);
} finally {
D.deb("Processing command output"); // NOI18N
//processCommandOutput();
D.deb("watchDog.cancel()"); // NOI18N
if (watchDog != null) watchDog.cancel();
}
D.deb("exec() -> "+getExitStatus()); // NOI18N
return getExitStatus();
}
/*
private void processCommandOutput() {
for(Enumeration elements = commandOutput.elements(); elements.hasMoreElements(); ) {
String what = (String) elements.nextElement();
if (elements.hasMoreElements()) {
if (what.equals(STDOUT)) {
stdoutNextLineCached((String) elements.nextElement());
} else {
stderrNextLineCached((String) elements.nextElement());
}
}
}
}
*/
//-------------------------------------------
private boolean stopThread(Thread t, SafeRunnable r){
// 1. be kind - just request stop
r.doStop();
long softTimeout=1000;
try{
t.join(softTimeout);
}catch (InterruptedException e){
D.deb(t.getName()+".join("+softTimeout+") after doStop() failed"); // NOI18N
// TODO
}
if( t.isAlive()==false ){
D.deb(t.getName()+" stopped after soft kill - great"); // NOI18N
return true;
}
// 2. be more hard - hey thread - do stop
t.interrupt();
long hardTimeout=1000;
try{
t.join(hardTimeout);
}catch (InterruptedException e){
D.deb(t.getName()+".join("+hardTimeout+") failed"); // NOI18N
// TODO
}
if( t.isAlive()==false ){
D.deb(t.getName()+" stopped after hard kill - good"); // NOI18N
return true;
}
// 3. last resort
t.stop();
long stopTimeout=1000;
try{
t.join(stopTimeout);
}catch (InterruptedException e){
}
if(t.isAlive()==false ){
D.deb(t.getName()+" stopped after stop() - at last"); // NOI18N
return true;
}
E.err("This shouldn't happen "+t.getName()+" is alive="+t.isAlive()); // NOI18N
return false;
}
//-------------------------------------------
public String toString(){
return command;
}
//-------------------------------------------
public class StdoutGrabber implements SafeRunnable {
private Debug D=new Debug("StdoutGrabber",true); // NOI18N
private boolean shouldStop=false;
private InputStreamReader is=null;
//-------------------------------------------
public StdoutGrabber(InputStream is){
this.is = new InputStreamReader(is);
}
//-------------------------------------------
public void doStop(){
shouldStop=true;
}
//-------------------------------------------
private void close(){
if(is!=null){
try{
is.close();
}catch (IOException e){
//E.err(e,"close() failed"); // NOI18N
}
}
}
//-------------------------------------------
public void run(){
//D.deb("stdout: run()"); // NOI18N
StringBuffer sb=new StringBuffer(80);
int b=-1;
try{
while( (b=is.read()) > -1 ){
char c = (char) b;
if( c== '\n' ){
String line=new String(sb);
//D.deb("stdout: <<"+line); // NOI18N
stdoutNextLine(line);
sb=new StringBuffer(80);
} else {
if( b!=13 ){
sb.append(c);
}
}
if(shouldStop){
D.deb("we should stop..."); // NOI18N
return;
}
}
}
catch(InterruptedIOException e){
D.deb("stdout: InterruptedIOException"); // NOI18N
}
catch(IOException e){
E.err(e,"stdout: read() failed"); // NOI18N
}
finally{
close();
}
//D.deb("stdout: run() finished"); // NOI18N
}
} //StdoutGrabber
//-------------------------------------------
public class StderrGrabber implements SafeRunnable {
private Debug D=new Debug("StderrGrabber",true); // NOI18N
private boolean shouldStop=false;
private InputStreamReader is=null;
//-------------------------------------------
public StderrGrabber(InputStream is){
this.is = new InputStreamReader(is);
}
//-------------------------------------------
public void doStop(){
shouldStop=true;
}
//-------------------------------------------
private void close(){
if(is!=null){
try{
is.close();
}catch (IOException e){
//E.err(e,"close() failed"); // NOI18N
}
}
}
//-------------------------------------------
public void run(){
//D.deb("stderr: run()"); // NOI18N
StringBuffer sb=new StringBuffer(80);
int b=-1;
try{
while( (b=is.read()) > -1 ){
char c=(char)b;
if( c== '\n' ){
String line=new String(sb);
//D.deb("stderr: <<"+line); // NOI18N
stderrNextLine(line);
sb=new StringBuffer(80);
} else {
if( b!=13 ){
sb.append(c);
}
}
if(shouldStop){
D.deb("we should stop..."); // NOI18N
return;
}
}
}
catch(InterruptedIOException e){
D.deb("stderr: InterruptedIOException"); // NOI18N
}catch(IOException e){
E.err(e,"stderr: read() failed"); // NOI18N
}
finally{
close();
}
//D.deb("stderr: run() finished"); // NOI18N
}
} //StderrGrabber
//-------------------------------------------
private RegexListener[] addListener(RegexListener[] la, RegexListener l){
int len=la.length;
RegexListener[] nla=new RegexListener[len+1];
System.arraycopy(la,0,nla,0,len);
nla[len]=l;
return nla;
}
//-------------------------------------------
private RE[] addRegex(RE[] ra, RE r){
int len=ra.length;
RE[] nra=new RE[len+1];
System.arraycopy(ra,0,nra,0,len);
nra[len]=r;
return nra;
}
//-------------------------------------------
public void addStdoutRegexListener(RegexListener l, String regex) throws BadRegexException {
synchronized(stdoutLock){
int len=stdoutListeners.length;
for(int i=0;i<len;i++){
if( stdoutListeners[i]==l ){
return;
}
}
RE pattern=null;
try{
pattern=new RE(regex);
}catch(RESyntaxException e){
//E.err(e,"RE failed regexp"); // NOI18N
throw new BadRegexException("Bad regexp.",e); // NOI18N
}
stdoutListeners=addListener(stdoutListeners,l);
stdoutRegexps=addRegex(stdoutRegexps,pattern);
}
}
//-------------------------------------------
public void addStderrRegexListener(RegexListener l, String regex) throws BadRegexException {
synchronized(stderrLock){
int len=stderrListeners.length;
for(int i=0;i<len;i++){
if( stderrListeners[i]==l ){
return;
}
}
RE pattern=null;
try{
pattern=new RE(regex);
}catch(RESyntaxException e){
//E.err(e,"RE failed regexp"); // NOI18N
throw new BadRegexException("Bad regexp.",e); // NOI18N
}
stderrListeners=addListener(stderrListeners,l);
stderrRegexps=addRegex(stderrRegexps,pattern);
}
}
//-------------------------------------------
public void addStdoutNoRegexListener(NoRegexListener l) {
synchronized(stdoutLock){
this.stdoutNoRegexListeners.addElement(l);
}
}
//-------------------------------------------
public void addStderrNoRegexListener(NoRegexListener l) {
synchronized(stderrLock){
this.stderrNoRegexListeners.addElement(l);
}
}
//-------------------------------------------
private int findInArray(RegexListener[] la, RegexListener l){
int len=la.length;
for(int i=0;i<len;i++){
if(la[i]==l){
return i;
}
}
return -1;
}
//-------------------------------------------
private RegexListener[] removeListenerAt(RegexListener[] la, int index){
int len=la.length;
RegexListener[] nla=new RegexListener[len-1];
/* e.g. We want to remove second element in 'la' index=1; len=4
la = [ a, b, c, d ]
nla = [ 0, 0, 0 ]
index = 1
*/
System.arraycopy(la,0,nla,0,len-1);
/*
la = [ a, b, c, d ]
nla = [ a, b, c ]
index = 1
*/
if( index!=len-1 ){
nla[index]=la[len-1];
}
/*
stdoutListeners = [ a, b, c, d ]
nla = [ a, d, c ]
index = 1
*/
return nla;
}
//-------------------------------------------
private RE[] removeRegexAt(RE[] ra, int index){
int len=ra.length;
RE[] nra=new RE[len-1];
System.arraycopy(ra,0,nra,0,len-1);
if( index != len-1 ){
nra[index]=ra[len-1];
}
return nra;
}
//-------------------------------------------
public void removeStdoutRegexListener(RegexListener l){
synchronized(stdoutLock){
int index=findInArray(stdoutListeners,l);
if(index<0){
return;
}
stdoutListeners=removeListenerAt(stdoutListeners,index);
stdoutRegexps=removeRegexAt(stdoutRegexps,index);
}
}
//-------------------------------------------
public void removeStderrRegexListener(RegexListener l){
synchronized(stderrLock){
int index=findInArray(stderrListeners,l);
if(index<0){
return;
}
stderrListeners=removeListenerAt(stderrListeners,index);
stderrRegexps=removeRegexAt(stderrRegexps,index);
}
}
//-------------------------------------------
private String[] matchToStringArray(RE pattern, String line){
Vector v=new Vector(5);
if (!pattern.match(line)) {
return new String[0];
}
for(int i=1; i < pattern.getParenCount(); i++){
int subStart=pattern.getParenStart(i);
int subEnd=pattern.getParenEnd(i);
if (subStart >= 0 && subEnd > subStart)
v.addElement(line.substring(subStart, subEnd));
}
int count=v.size();
if (count <= 0) count = 1;
String[]sa=new String[count];
v.toArray(sa);
return sa;
}
/*
public synchronized void stdoutNextLine(String line){
synchronized(stdOutErrLock) {
commandOutput.addElement(STDOUT);
commandOutput.addElement(line);
}
}
*/
//-------------------------------------------
public synchronized void stdoutNextLine(String line){
synchronized(stdOutErrLock) {
synchronized(stdoutLock){
//D.deb("stdout <<"+line); // NOI18N
int len=stdoutListeners.length;
for(int i=0;i<len;i++){
RE pattern=stdoutRegexps[i];
String[] sa=matchToStringArray(pattern, line);
if (sa != null && sa.length > 0) stdoutListeners[i].match(sa);
}
// call No Regex Listeners, which match the whole line
Enumeration enum = stdoutNoRegexListeners.elements();
while(enum.hasMoreElements()) {
((NoRegexListener) enum.nextElement()).match(line);
}
}
}
}
/*
public synchronized void stderrNextLine(String line){
synchronized(stdOutErrLock) {
commandOutput.addElement(STDERR);
commandOutput.addElement(line);
}
}
*/
//-------------------------------------------
public void stderrNextLine(String line){
synchronized(stdOutErrLock) {
synchronized(stderrLock){
//D.deb("stderr <<"+line); // NOI18N
int len=stderrListeners.length;
for(int i=0;i<len;i++){
RE pattern=stderrRegexps[i];
String[] sa=matchToStringArray(pattern, line);
if (sa != null && sa.length > 0) stderrListeners[i].match(sa);
}
// call No Regex Listeners, which match the whole line
Enumeration enum = stderrNoRegexListeners.elements();
while(enum.hasMoreElements()) {
((NoRegexListener) enum.nextElement()).match(line);
}
}
}
}
//-------------------------------------------
String g(String s) {
return NbBundle.getBundle
("org.netbeans.modules.vcs.cmdline.Bundle").getString (s);
}
String g(String s, Object obj) {
return MessageFormat.format (g(s), new Object[] { obj });
}
String g(String s, Object obj1, Object obj2) {
return MessageFormat.format (g(s), new Object[] { obj1, obj2 });
}
String g(String s, Object obj1, Object obj2, Object obj3) {
return MessageFormat.format (g(s), new Object[] { obj1, obj2, obj3 });
}
//-------------------------------------------
}
/*
* Log
* 12 Gandalf-post-FCS1.10.2.0 3/23/00 Martin Entlicher InputStream changed to
* inputStreamReader for good localization, synchronization modified.
* 11 Gandalf 1.10 2/8/00 Martin Entlicher
* 10 Gandalf 1.9 1/15/00 Ian Formanek NOI18N
* 9 Gandalf 1.8 1/6/00 Martin Entlicher
* 8 Gandalf 1.7 12/20/99 Martin Entlicher
* 7 Gandalf 1.6 12/16/99 Martin Entlicher
* 6 Gandalf 1.5 11/30/99 Martin Entlicher
* 5 Gandalf 1.4 10/25/99 Pavel Buzek
* 4 Gandalf 1.3 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 3 Gandalf 1.2 10/7/99 Pavel Buzek
* 2 Gandalf 1.1 10/5/99 Pavel Buzek VCS at least can be
* mounted
* 1 Gandalf 1.0 9/30/99 Pavel Buzek
* $
*/